/**
 *
 */
package de.cbf.cam.dsa.person.generator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.cbf.cam.dsa.item.model.entities.Item;
import de.cbf.cam.dsa.item.model.entities.interfaces.IGenderSpecific;
import de.cbf.cam.dsa.item.model.interfaces.IItemResolver;
import de.cbf.cam.dsa.person.entities.PersonBaseDSA;
import de.cbf.cam.dsa.person.entities.PersonItemsDSA;
import de.cbf.cam.dsa.rcp.model.entities.CultureDSA;
import de.cbf.cam.dsa.rcp.model.entities.ProfessionDSA;
import de.cbf.cam.dsa.rcp.model.entities.items.CultureItemDSA;
import de.cbf.cam.dsa.rcp.model.entities.items.ItemForProfession;
import de.cbf.cam.dsa.rcp.model.entities.items.ItemForProfessionAlternatives;
import de.cbf.cam.dsa.rcp.model.entities.items.ItemForProfessionTypical;
import de.cbf.cam.dsa.rcp.model.entities.items.ProfessionItemDSA;
import de.cbf.util.number_generator.RandomNumberGenerator;
import de.cbf.util.uuidheader.entities.EntityWeighted;
import de.cbf.util.uuidheader.entities.UUIDHeader;
import de.cbf.util.uuidheader.entities.UUIDReference;
import de.cbf.util.uuidheader.util.EntityCache;
import de.cbf.util.uuidheader.util.EntityWeightedList;

/**
 * @author salter
 *
 */
public class ItemGenerator {
	private static final Log logger = LogFactory.getLog(ItemGenerator.class);

	protected IItemResolver resolver;

	protected EntityCache itemCache;



	public ItemGenerator() {
		super();
		init();
	}

	protected void init() {
		itemCache = new EntityCache();
	}

	public void generateItems(PersonBaseDSA person, CultureDSA culture,
			ProfessionDSA profession, Map<String, Object> properties) {

		int gender = person.getGender();

		PersonItemsDSA personItems = person.getItems();
		if(personItems == null ){
			personItems = new PersonItemsDSA();
		}

		if (profession instanceof ProfessionItemDSA) {
			ProfessionItemDSA professionItemsDSA = (ProfessionItemDSA) profession;
			List<ItemForProfession> items = professionItemsDSA.getProfessionItems();
			for (ItemForProfession itemForProfession : items) {
				List<Item> foundItems = resolveItem(itemForProfession,culture,gender);
				personItems.addItems(foundItems);
			}
		}else{
			logger.warn("Profession does not hold any items to calculate items from");
		}
		Object environment = null;
		personItems.takeOnClothes(environment,gender);
		person.setItems(personItems);
	}

	protected List<Item> resolveItem(ItemForProfession itemForProfession, CultureDSA culture,int gender){
		List<Item> items = new ArrayList<Item>();
		if (itemForProfession instanceof ItemForProfessionAlternatives) {
			ItemForProfessionAlternatives itemWithAlternatives = (ItemForProfessionAlternatives) itemForProfession;
			Item chosenItem = resolveItemAlternatives(itemWithAlternatives,gender);
			chooseQuality(chosenItem);
			items.add(chosenItem);
			return items;
		}
		if (itemForProfession instanceof ItemForProfessionTypical) {
			ItemForProfessionTypical itemTypical = (ItemForProfessionTypical) itemForProfession;
			return resolveCultureTypicalItem(itemTypical,culture,gender);
		}
		Item item = loadItem(itemForProfession.getItemReference());
		chooseQuality(item);
		items.add(item);
		return items;

//		logger.error();
//		throw new ItemResolutionException("resolveAttributes are filled and do not contain a known identifier: "+resolveAttributes);
	}


	protected void chooseQuality(Item item) {
		Double minQuality = item.getQualityMin();
		Double maxQuality = item.getQualityMax();
		Double resultQuality = RandomNumberGenerator.getInstance().findRandomBetweenMinMax(minQuality, maxQuality,true);

		item.setQuality(resultQuality);
	}

	protected Item resolveItemAlternatives(ItemForProfession itemForProfession,int gender){
		if (itemForProfession instanceof ItemForProfessionAlternatives) {
			//cast
			ItemForProfessionAlternatives itemAlternatives = (ItemForProfessionAlternatives) itemForProfession;
			Double desiredQuality = itemForProfession.getDesiredQuality();
			//get alternative items to choose from
			EntityWeightedList alternativeItems = itemAlternatives.getAlternativeItems();
			//filter for gender
			filterItems(alternativeItems,desiredQuality,gender);
			//choose
			EntityWeighted entityWeighted = alternativeItems.chooseEntityByWeight();
			//extract item
			UUIDHeader uuidHeader = entityWeighted.getEntity()!=null?entityWeighted.getEntity():null;//check if chosen entity is null
			if (uuidHeader instanceof UUIDReference) {
				UUIDReference reference = (UUIDReference)uuidHeader;
				Item item = loadItem(reference);
				return item;
			}else{
				logger.error("EntityWeighted does not contain Item.class, but: "+uuidHeader.getClass().getSimpleName());
				return null;
			}
		}else{
			logger.error("ItemForProfession flagged as ItemForProfessionAlternatives.class, but wrong class: "+itemForProfession.getClass().getSimpleName());
			return null;
		}
	}

	protected List<Item> resolveCultureTypicalItem(ItemForProfessionTypical itemForProfession,
			CultureDSA culture, int gender) {

		List<Item> result = new ArrayList<Item>();

		if (culture instanceof CultureItemDSA) {
			CultureItemDSA cultureItem = (CultureItemDSA) culture;
			//filter for desiredQuality standards
			Double desiredQuality = itemForProfession.getDesiredQuality();

			if(isGeneralClothingItem(itemForProfession)){
				Collection<String> keys = generateGeneralClothingKeys();
				for (String key : keys) {
					Item item = chooseCulturalItemByKey(cultureItem, key, desiredQuality, gender);
					if(item != null){
						result.add(item);
					}
				}
			}else{
				//item may hold several keys, add up items to choose from
				EntityWeightedList possibleItems = new EntityWeightedList();
				possibleItems.setSumAndMaxCalculationActive(false);

				Collection<String> keys = itemForProfession.getResolveAttributes();
				if(keys == null || keys.isEmpty()){
					keys = cultureItem.getCultureItems().keySet();
				}
				for (String key : keys) {
					EntityWeightedList items = cultureItem.getTypedCultureItems(key);
					possibleItems.addAll(items);
				}
				//filter for gender and desiredQuality
				filterItems(possibleItems, desiredQuality, gender);

				possibleItems.setSumAndMaxCalculationActive(true);
				//choose reference and load item
				EntityWeighted entityWeighted = possibleItems.chooseEntityByWeight();
				if (entityWeighted.getEntity() instanceof UUIDReference) {
					UUIDReference uuidReference = (UUIDReference) entityWeighted.getEntity();
					result.add(loadItem(uuidReference));
				}
			}
		}
		return result;
	}

	/**
	 * find out if the item wants us to create a clothing set
	 * @param itemForProfession
	 * @return
	 */
	protected boolean isGeneralClothingItem(ItemForProfessionTypical itemForProfession) {
		List<String> attributes = itemForProfession.getResolveAttributes();
		for (String attribute : attributes) {
			if(CultureItemDSA.KEY_CLOTHING.equals(attribute)){
				return true;
			}
		}
		return false;
	}

	/**
	 * keys a clothing set consists of
	 * @return
	 */
	protected Collection<String> generateGeneralClothingKeys() {
		List<String> result = new ArrayList<String>();//TODO maybe get this from culture
		result.add(CultureItemDSA.KEY_CLOTHING_UNDERWEAR);
		result.add(CultureItemDSA.KEY_CLOTHING_HAT);
		result.add(CultureItemDSA.KEY_CLOTHING_SHIRT);
		result.add(CultureItemDSA.KEY_CLOTHING_JACKET);
		result.add(CultureItemDSA.KEY_CLOTHING_CLOAK);
		result.add(CultureItemDSA.KEY_CLOTHING_TROUSERS);
		result.add(CultureItemDSA.KEY_CLOTHING_SHOES);
//		result.add(CultureItemDSA.KEY_CLOTHING);

		return result;
	}


	protected Item chooseCulturalItemByKey(CultureItemDSA cultureItem, String key, Double quality, int gender) {
		EntityWeightedList possibleItems = cultureItem.getTypedCultureItems(key);
		if(possibleItems == null || possibleItems.isEmpty() ){
			logger.error("no items found for "+key+", cannot chooseCulturalItemByKey -> returns null");
			return null;
		}
		filterItems(possibleItems, quality, gender);
		if(possibleItems == null || possibleItems.isEmpty() ){
			logger.error("no items found (after filtering) for "+key+", cannot chooseCulturalItemByKey -> returns null");
			return null;
		}

		EntityWeighted entityWeighted = possibleItems.chooseEntityByWeight();
		if(entityWeighted == null){
			logger.error("could not choose cultural item");
			return null;
		}
		if (entityWeighted.getEntity() instanceof UUIDReference) {
			UUIDReference uuidReference = (UUIDReference) entityWeighted.getEntity();
			return loadItem(uuidReference);
		}
		return null;
	}


	protected void filterItems(EntityWeightedList possibleItems,Double quality, int gender){
		possibleItems.setSumAndMaxCalculationActive(false);
		//filter for gender
		filterItemsForGender(possibleItems, gender);
		filterItemsForQuality(possibleItems,quality);
		possibleItems.setSumAndMaxCalculationActive(true);
	}

	protected void filterItemsForGender(EntityWeightedList alternativeItems,int gender) {
		alternativeItems.setSumAndMaxCalculationActive(false);
		Iterator<EntityWeighted> it = alternativeItems.iterator();
		while (it.hasNext()) {
			EntityWeighted entityWeighted = (EntityWeighted) it.next();
			UUIDHeader entity = entityWeighted.getEntity();
			if (entity instanceof UUIDReference) {
				UUIDReference reference = (UUIDReference) entity;
				//resolve unresolved items to decide gender specifics
				entity =  loadItem(reference);
			}
			if (entity instanceof IGenderSpecific) {
				IGenderSpecific genderSpecific = (IGenderSpecific) entity;
				int itemGender = genderSpecific.getGender();
				if( 	! (itemGender == IGenderSpecific.GENDER_UNKNOWN)
					&&	! (itemGender == gender)	){
					//item gender is not both and not gender -> does not fit -> remove
					it.remove();
				}
			}

		}
		alternativeItems.setSumAndMaxCalculationActive(true);
	}

	protected void filterItemsForQuality(EntityWeightedList possibleItems,
			Double desiredQuality) {
		possibleItems.setSumAndMaxCalculationActive(false);
		Iterator<EntityWeighted> it = possibleItems.iterator();
		while (it.hasNext()) {
			EntityWeighted entityWeighted = (EntityWeighted) it.next();
			UUIDHeader entity = entityWeighted.getEntity();
			if (entity instanceof UUIDReference) {
				UUIDReference reference = (UUIDReference) entity;
				//resolve unresolved items to decide gender specifics
				Item item = loadItem(reference);
				entity = item;
			}
			if (entity instanceof Item) {
				Item item = (Item) entity;
				if(	   (item.getQualityMax() < desiredQuality)
					|| (item.getQualityMin() > desiredQuality)){
					//item desiredQuality does not fit -> remove
					it.remove();
				}
			}
		}
		possibleItems.setSumAndMaxCalculationActive(true);
	}

	/**
	 * load item, from cache if it is there
	 * @param itemReference
	 * @return
	 */
	protected Item loadItem(UUIDReference itemReference) {
		Item result = null;
		//make cache and regard it, if resolver does not cache
		if(resolver.usesCache()){
			result = resolver.resolveItem(itemReference.getSavedUuid());
		}else{
			boolean cached = itemCache.isCached(itemReference.getSavedUuid());
			if(cached){
				result = (Item)itemCache.getEntity(itemReference.getSavedUuid());//can, should only be items
			}else{
				//not cached load from DB
				Item item = resolver.resolveItem(itemReference.getSavedUuid());
				itemCache.cacheEntity(item);
				result = item;
			}
		}
		return result;
	}

	public IItemResolver getResolver() {
		return resolver;
	}

	public void setResolver(IItemResolver resolver) {
		this.resolver = resolver;
	}

}
